/*
	The Raja Toolkit.
	Copyright (C) 2011 Raja-Toolkit Foundation

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation; either
	version 3.0 of the License, or (at your option) any later version.

	This library is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/


#ifndef RAJA_MATH_QUATERNION
#define RAJA_MATH_QUATERNION

#include "../types.hpp"
#include "vector.hpp"
#include "matrix.hpp"

namespace raja
{
namespace math
{
template<typename T>
struct tquat
{
	union
	{
		struct
		{
			T	a, b, c, d;
		};
		struct
		{
			T	x, y, z, w;
		};
	};

	explicit inline tquat() :
		x(0), y(0), z(0), w(1)
	{
	}
	explicit inline tquat(T _x, T _y, T _z, T _w) :
		x(_x), y(_y), z(_z), w(_w)
	{
	}
	inline tquat(const tquat &q) :
		x(q.x), y(q.y), z(q.z), w(q.w)
	{
	}
	template<typename Y> explicit inline tquat(const tvec4<Y> &v) :
		x(v.x), y(v.y), z(v.z), w(v.w)
	{
	}

	tquat(const tmat3<T> &m);
	tquat(const tmat4<T> &m);
	tquat(const tvec3<T> &axis, T angle);

	/// access
	T& operator[](int i)
	{
		return (&x)[i];
	}

	///
	/// operators
	///

	/// addition
	tquat& operator +=(const tquat &q)
	{
		x += q.x;
		y += q.y;
		z += q.z;
		w += q.w;
		return (*this);
	}

	tquat& operator +=(T t)
	{
		x += t;
		y += t;
		z += t;
		w += t;
		return (*this);
	}

	/// substraction
	tquat& operator -=(const tquat &q)
	{
		x -= q.x;
		y -= q.y;
		z -= q.z;
		w -= q.w;
		return (*this);
	}

	tquat& operator -=(T t)
	{
		x -= t;
		y -= t;
		z -= t;
		w -= t;
		return (*this);
	}

	/// multiplication
	tquat& operator *=(const tquat &q);

	tquat& operator *=(T t)
	{
		x *= t;
		y *= t;
		z *= t;
		w *= t;
		return (*this);
	}

	/// division
	tquat& operator /=(const tquat &q);

	tquat& operator /=(T t)
	{
		x /= t;
		y /= t;
		z /= t;
		w /= t;
		return (*this);
	}

	/// prefix increment
	tquat& operator++()
	{
		++x;
		++y;
		++z;
		++w;
		return (*this);
	}

	/// prefix decrement
	tquat& operator--()
	{
		--x;
		--y;
		--z;
		--w;
		return (*this);
	}

	/// postfix increment
	tquat operator++(int)
	{
		tquat res(*this);
		++(*this);
		return res;
	}

	/// postfix decrement
	tquat operator--(int)
	{
		tquat res(*this);
		--(*this);
		return res;
	}

	/// assignment
	tquat& operator =(const tquat &q)
	{
		x = q.x;
		y = q.y;
		z = q.z;
		w = q.w;
		return (*this);
	}
};

/// unary +
template<typename T>
inline tquat<T> operator + (const tquat<T> &q)
{
	return q;
}

/// unary -
template<typename T>
inline tquat<T> operator - (const tquat<T> &q)
{
	return tquat<T>(-q.x, -q.y, -q.z, -q.w);
}

/// addition
template<typename T>
inline tquat<T> operator + (const tquat<T> &q0, const tquat<T> &q1)
{
	return tquat<T>(q0.x + q1.x, q0.y + q1.y, q0.z + q1.z, q0.w + q1.w);
}

template<typename T>
inline tquat<T> operator + (const tquat<T> &q0, T q1)
{
	return tquat<T>(q0.x + q1, q0.y + q1, q0.z + q1, q0.w + q1);
}

template<typename T>
inline tquat<T> operator + (T q0, const tquat<T> &q1)
{
	return tquat<T>(q0 + q1.x, q0 + q1.y, q0 + q1.z, q0 + q1.w);
}

/// substraction
template<typename T>
inline tquat<T> operator - (const tquat<T> &q0, const tquat<T> &q1)
{
	return tquat<T>(q0.x - q1.x, q0.y - q1.y, q0.z - q1.z, q0.w - q1.w);
}

template<typename T>
inline tquat<T> operator - (const tquat<T> &q0, T q1)
{
	return tquat<T>(q0.x - q1, q0.y - q1, q0.z - q1, q0.w - q1);
}

template<typename T>
inline tquat<T> operator - (T q0, const tquat<T> &q1)
{
	return tquat<T>(q0 - q1.x, q0 - q1.y, q0 - q1.z, q0 - q1.w);
}

/// multiplication
template<typename T>
inline tquat<T> operator * (const tquat<T> &q0, T q1)
{
	return tquat<T>(q0.x * q1, q0.y * q1, q0.z * q1, q0.w * q1);
}

template<typename T>
inline tquat<T> operator * (T q0, const tquat<T> &q1)
{
	return tquat<T>(q0 * q1.x, q0 * q1.y, q0 * q1.z, q0 * q1.w);
}

/// division
template<typename T>
inline tquat<T> operator / (const tquat<T> &q0, T q1)
{
	return tquat<T>(q0.x / q1, q0.y / q1, q0.z / q1, q0.w / q1);
}

template<typename T>
inline tquat<T> operator / (T q0, const tquat<T> &q1)
{
	return tquat<T>(q0 / q1.x, q0 / q1.y, q0 / q1.z, q0 / q1.w);
}

/// comparison
template<typename T>
inline bool operator == (const tquat<T> &q0, const tquat<T> &q1)
{
	return (q0.x == q1.x &&
			q0.y == q1.y &&
			q0.z == q1.z &&
			q0.w == q1.w);
}

template<typename T>
inline bool operator != (const tquat<T> &q0, const tquat<T> &q1)
{
	return !(q0 == q1);
}

/// dot
template<typename T>
inline T dot(const tquat<T> &q0, const tquat<T> &q1)
{
	return (q0.x * q1.x + q0.y * q1.y + q0.z * q1.z + q0.w * q1.w);
}

/// length
template<typename T>
inline T length(const tquat<T>& q)
{
	return std::sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w);
}

/// conjugate
template<typename T>
inline tquat<T> conjugate(const tquat<T>& q)
{
	return tquat<T>(-q.x, -q.y, -q.z, q.w);
}

/// normalize
template<typename T>
inline tquat<T> normalize(const tquat<T>& q)
{
	return q / length(q);
}

/// inverse
template<typename T>
inline tquat<T> inverse(const tquat<T>& q)
{
	return normalize(conjugate(q));
}

/// multiply
template<typename T>
tquat<T> operator * (const tquat<T> &q0, const tquat<T> &q1);

/// division
template<typename T>
tquat<T> operator / (const tquat<T> &q0, const tquat<T> &q1);

/// conversion
template<typename T>
tmat3<T> mat3_from(const tquat<T>& q);

template<typename T>
tmat4<T> mat4_from(const tquat<T>& q);

template<typename T>
void quat_to_axis_angle(const tquat<T>& q, tvec3<T>& axis, T &angle);

/// spherical interpolation
template<typename T>
tquat<T> slerp(const tquat<T>& from, const tquat<T> &to, T t);

typedef tquat<real32>		quat;
typedef tquat<real64>		dquat;

} // namespace math
} // namespace raja
#endif // RAJA_MATH_QUATERNION
